///
/// 66.71 Sistemas Graficos
/// Framework de trabajo para el TP1
/// 2do Cuatrimestre 2010
///
/// info@sg6671.com.ar
///

#include <GL/glut.h>
#include <math.h>
#include <stdlib.h>
#include <iostream>

#include "camara.h"

#include "coord.h"

#include "bscerrada.h"
#include "bsanclada.h"
#include "bsabierta.h"

#include "bezier.h"

#include "superficie.h"
#include "curva.h"

#include "textura.h"
#include "texels.h"

#include "discretizador.h"

#include "roca.h"

#include "mapa.h"
#include "editorRevoluciones.h"
#include "editorPerfil.h"
#include "iobmp.h"
#include "skybox.h"
#include <vector>

Camara *cam = 0;

Mapa* mapa = new Mapa(40,40);
editorRevoluciones* editorRev = new editorRevoluciones(mapa);
editorPerfil* editorPerf = 0;
Skybox* skybox;

Bezier *perfil = 0;

Textura *roca = 0;
Textura *pasto = 0;

Discretizador discretizador;

// Variables que controlan la ubicaci�n de la c�mara en la Escena 3D
float eye[3] = {15.0, 15.0, 5.0};
float at[3]  = { 0.0,  0.0, 0.0};
float up[3]  = { 0.0,  0.0, 1.0};

// Color de la esfera en movimiento dentro de la escena
float color_esfera[4] = {0.5f, 0.5f, 0.2f, 1.0f};

// Variable asociada al movimiento de rotaci�n de la esfera alrededor del eje Z
float rotate_sphere = 0;

// Variables de control
bool view_grid = false;
bool view_axis = true;
bool edit_panel = false;


// Handle para el control de las Display Lists
GLuint dl_handle;
#define DL_AXIS (dl_handle+0)
#define DL_GRID (dl_handle+1)
#define DL_AXIS2D_TOP (dl_handle+2)

// Tama�o de la ventana
GLfloat window_size[2];
#define W_WIDTH window_size[0]
#define W_HEIGHT window_size[1]

#define TOP_VIEW_POSX	((int)((float)W_WIDTH*0.80f))
#define TOP_VIEW_W		((int)((float)W_WIDTH*0.20f))
#define TOP_VIEW_POSY	((int)((float)W_HEIGHT*0.60f))
#define TOP_VIEW_H		((int)((float)W_HEIGHT*0.40f))

#define BOTTOM_VIEW_POSX	((int)((float)W_WIDTH*0.80f))
#define BOTTOM_VIEW_W		((int)((float)W_WIDTH*0.20f))
#define BOTTOM_VIEW_POSY	((int)((float)W_HEIGHT*0.20f))
#define BOTTOM_VIEW_H		((int)((float)W_HEIGHT*0.40f))

void OnIdle (void)
{
	rotate_sphere += 0.1f;
	if(rotate_sphere > 360.0) rotate_sphere = 0.0;
    glutPostRedisplay();
}

void DrawAxis()
{
	glDisable(GL_LIGHTING);
	glBegin(GL_LINES);
		// X
		glColor3f(1.0, 0.0, 0.0);
		glVertex3f(0.0, 0.0, 0.0);
		glColor3f(0.0, 0.0, 0.0);
		glVertex3f(15.0, 0.0, 0.0);
		// Y
		glColor3f(0.0, 1.0, 0.0);
		glVertex3f(0.0, 0.0, 0.0);
		glColor3f(0.0, 0.0, 0.0);
		glVertex3f(0.0, 15.0, 0.0);
		// Z
		glColor3f(0.0, 0.0, 1.0);
		glVertex3f(0.0, 0.0, 0.0);
		glColor3f(0.0, 0.0, 0.0);
		glVertex3f(0.0, 0.0, 15.0);
	glEnd();
	glEnable(GL_LIGHTING);
}

void DrawAxis2DTopView()
{
	glDisable(GL_LIGHTING);
	glBegin(GL_LINE_LOOP);
		// X
		glColor3f(0.0f, 0.5f, 1.0f);
		glVertex3f(0.0f, 0.0f, 0.0f);
		glVertex3f(1.0f, 0.0f, 0.0f);
		glVertex3f(1.0f, 1.0f, 0.0f);
		glVertex3f(0.0f, 1.0f, 0.0f);
	glEnd();
	glBegin(GL_QUADS);
		glColor3f(0.1f, 0.1f, 0.1f);
		glVertex3f(0.0f, 0.0f, -0.01f);
		glVertex3f(1.0f, 0.0f, -0.01f);
		glVertex3f(1.0f, 1.0f, -0.01f);
		glVertex3f(0.0f, 1.0f, -0.01f);
	glEnd();

	glEnable(GL_LIGHTING);
}


void DrawXYGrid()
{
	int i;
	glDisable(GL_LIGHTING);
	glColor3f(0.15f, 0.1f, 0.1f);
	glBegin(GL_LINES);
	for(i=-20; i<21; i++)
	{
		glVertex3f((float)i, -20.0f, 0.0f);
		glVertex3f((float)i,  20.0f, 0.0f);
		glVertex3f(-20.0f, (float)i, 0.0f);
		glVertex3f( 20.0f, (float)i, 0.0f);
	}
	glEnd();
	glEnable(GL_LIGHTING);
}
void Set3DEnv()
{
	glViewport (0, 0, (GLsizei) W_WIDTH, (GLsizei) W_HEIGHT); 
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    gluPerspective(60.0, (GLfloat) W_WIDTH/(GLfloat) W_HEIGHT, 0.10, 100.0);
}

void SetPanelTopEnv()
{
	glViewport (TOP_VIEW_POSX, TOP_VIEW_POSY, (GLsizei) TOP_VIEW_W, (GLsizei) TOP_VIEW_H); 
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    gluOrtho2D(0, 1,0, 1);
}


void SetPanelBottomEnv(){
	glViewport (BOTTOM_VIEW_POSX, BOTTOM_VIEW_POSY, (GLsizei) BOTTOM_VIEW_W, (GLsizei) BOTTOM_VIEW_H); 
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
	gluOrtho2D(0, 1, 0, 1);
}

void init(void) 
{

   glEnable(GL_RESCALE_NORMAL);

   IObmp io;
   Texels *tmp;

   tmp = &io.cargarBMP("roca.bmp");
   roca = new Textura(*tmp);
   delete tmp;

   mapa->setTexturaRoca(roca);

   tmp = &io.cargarBMP("pasto.bmp");
   pasto = new Textura(*tmp);
   delete tmp;

   skybox = new Skybox(mapa->ancho());
   // Camara esferica, con plano rotacional del ecuador igual 
   // al plano xy
   cam = new Camara(1, 0, 0, 0, 0, 1);

   // Ampliamos la esfera (zoom out) 15 unidades
   cam->distancia(15);

   // Definimos una elevacion respecto al plano del ecuador: 
   //      latitud = 20 grados
   // Rotamos respecto al eje principal:
   //      longitud = 45 grados
   cam->latitud(45);
   cam->longitud(45);

   //CONTRUCCIOND DE LA Bezier (perfil inicial)
   std::vector<Coord> puntosControlBezier(4);
   puntosControlBezier[0].set(1,   0, 3);
   puntosControlBezier[1].set(2,   0, 2.5); 
   puntosControlBezier[2].set(2.5, 0, 1); 
   puntosControlBezier[3].set(3,   0, 0.5);

   perfil = new Bezier(puntosControlBezier);
   editorPerf = new editorPerfil(perfil,mapa);

   dl_handle = glGenLists(3);
   glClearColor (0.02f, 0.02f, 0.04f, 0.0f);
   //glClearColor (1, 1, 1, 0.0f);
   glShadeModel (GL_SMOOTH);
   glEnable(GL_DEPTH_TEST);
   skybox->setDaylightIllum();
   glColorMaterial(GL_FRONT_AND_BACK,GL_DIFFUSE);

   // Generaci�n de las Display Lists
   glNewList(DL_AXIS, GL_COMPILE);
   DrawAxis();
   glEndList();
   glNewList(DL_GRID, GL_COMPILE);
   DrawXYGrid();
   glEndList();
   glNewList(DL_AXIS2D_TOP, GL_COMPILE);
   DrawAxis2DTopView();
   glEndList();

   //HEY!
   glCullFace(GL_BACK);
   glEnable(GL_CULL_FACE);



}


void display(void)
{
	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	///////////////////////////////////////////////////
	// Escena 3D
	Set3DEnv();

        glMatrixMode(GL_TEXTURE);
	glLoadIdentity();
        glScalef(5, 5, 1);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	cam->x(0);
	cam->y(0);
	cam->z(0);

    	cam->ver();

   	if (view_axis)
		 glCallList(DL_AXIS);
	
	if (view_grid)
		 glCallList(DL_GRID);
	//
	///////////////////////////////////////////////////
    ///////////////////////////////////////////////////
	//
	// Draw here
	//
        glEnable(GL_TEXTURE_2D);

        //ATENCION!!!!
        //Para que una Superficie se vea real, hay que usar las luces!!
        //En conjunto, cada "Material" debe determinar que color debe reflejar
        //de forma Specular, Diffusa, y de Ambiente.
        //
        //Es muy tedioso andar definiendo el material para cada superficie
        //
        //Para simplificar, la siguiente instruccion que fue definida en init()
        //permite asociar las instruccion glColor a instrucciones glMaterial
        //Cada vez que hacemos un glColor, estamos definiendo el material
        //asociado con el color Difuso.
        //
        //        glColorMaterial(GL_FRONT_AND_BACK,GL_DIFFUSE);
        //
        //Si la superficie representa un objeto "rojo", se deberia usar
        //  glColor(1, 0, 0) tal como se hizo en otros TPs
        //
        //Ahora bien, cuando hay una Textura aplicada puede surgir dos cosas:
        //  - Usamos el color de la Textura (REMPLAZO) perdiendo asi el color del
        //     material y las luces. Es muy simple pero poco realista!.
        //  - Usamos el color de la Textura en conjunto con la "iluminacion" (MODULAR)
        //     Este es el default de la clase Textura, y modula los texels con la
        //     "iluminacion". Esta "iluminacion" es un color resultante entre la 
        //     combinacion de los colores de las luces y los colores de los materiales.
        //
        //     Para no "alterar" los colores de los texels, se suele usar un color "neutro"
        //     en los materiales, generalmente un gris o un blanco.
        //
        //     Pueden probar otros colores para que vean el resultado!
        //
	glEnable(GL_COLOR_MATERIAL);
        glColor3f(1, 1, 1);

        mapa->dibujarSuelo(pasto);
        //*low_shininess = 5.0;
        //glMaterialfv(GL_FRONT, GL_SHININESS, low_shininess);

        glPushMatrix();
	glTranslatef(-(mapa->ancho())/2,-(mapa->alto())/2,0);

        Discretizador d;

        list<Roca*>* rocas = mapa->getAlmacenRocas();
        list<Roca*>::iterator itrocas;
			for ( itrocas=rocas->begin() ; itrocas != rocas->end(); itrocas++ ){
				(*itrocas)->dibujar();
			}
        glPopMatrix();

        GLfloat mat_emission[] = {1.0, 1.0, 1.0, 0.5};
        glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);
        //*low_shininess = 128.0;
        //glMaterialfv(GL_FRONT, GL_SHININESS, low_shininess);
        skybox->dibujar();

	glDisable(GL_COLOR_MATERIAL);

	//
	///////////////////////////////////////////////////


	///////////////////////////////////////////////////
	// Panel 2D para la vista superior
//	if (edit_panel)
//	{
		SetPanelTopEnv();
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		gluLookAt (0, 0, 0.5, 0, 0, 0, 0, 1, 0);
		glCallList(DL_AXIS2D_TOP);
        glDisable(GL_LIGHTING);
        glPointSize(5);
        mapa->dibujarMiniMapa();
        editorRev->dibujarPuntosControl();
        glPointSize(5);
        /*glBegin(GL_POINTS);
            glColor3f(1.0,0.0,0.0);
            glVertex2f(0.5,0.5);
        glEnd();*/
        SetPanelBottomEnv();
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		gluLookAt (0, 0, 0.5, 0, 0, 0, 0, 1, 0);
		glCallList(DL_AXIS2D_TOP);

                //ATENCION!!!! XXX
                //
                //La display list anterior setea GL_LIGHTING
                //Pero la curva a dibujar por el editor de perfil no esta pensada
                //para dibujarse sobre una escena iluminada
                //
                //Es necesario deshabilitar GL_LIGHTING
                glDisable(GL_LIGHTING);

		editorPerf->dibujarPanelPerfil();

                glEnable(GL_LIGHTING);


	//
	///////////////////////////////////////////////////
	
	

	glutSwapBuffers();
}

void reshape (int w, int h)
{
   	W_WIDTH  = (float)w;
	W_HEIGHT = (float)h;
}

int anteriorx = 0;
int anteriory = 0;
bool iniciado = false;

bool clickPanelSuperior(int x, int y, int state){
    return ((x > TOP_VIEW_POSX) && (x < TOP_VIEW_POSX + TOP_VIEW_W) && (y < W_HEIGHT - TOP_VIEW_POSY) && (y > W_HEIGHT - TOP_VIEW_POSY - TOP_VIEW_H) && (state==GLUT_UP));
}

bool clickPanelInferior(int x, int y, int state){
    return (x > BOTTOM_VIEW_POSX && x < BOTTOM_VIEW_POSX + BOTTOM_VIEW_W && y < (W_HEIGHT - BOTTOM_VIEW_POSY) && y > (W_HEIGHT - BOTTOM_VIEW_POSY - BOTTOM_VIEW_H) && (state==GLUT_UP));
}

void mouse( int x, int y) {
   if(!iniciado) {
      anteriorx = x;
      anteriory = y;
      
      iniciado = true;
      return;
   }
   int deltaX = x-anteriorx;
   int deltaY = y-anteriory;
   if ((deltaX<40)&&(deltaY<40)){
	   cam->longitud( cam->longitud() + (deltaX) / 3.0 );
	   if ((cam->latitud() + (deltaY) / 3.0) > 5)
		   if ((cam->latitud() + (deltaY) / 3.0) <= 90)
			   cam->latitud( cam->latitud() + (deltaY) / 3.0 );
   }

   anteriorx = x;
   anteriory = y;
}

void mouseClick(int boton,int estado,int x, int y){
	enum
	{
		MOUSE_LEFT_BUTTON = 0,
		MOUSE_MIDDLE_BUTTON = 1,
		MOUSE_RIGHT_BUTTON = 2,
		MOUSE_SCROLL_UP = 3,
		MOUSE_SCROLL_DOWN = 4
	};
    if(clickPanelSuperior(x,y,estado)){
    	if (boton==MOUSE_LEFT_BUTTON){
    		//Obtencion de las coordenadas normalizadas
    		float deltaX = x-TOP_VIEW_POSX;
    		float deltaY = TOP_VIEW_POSY-y;
    		float normaX = deltaX/(TOP_VIEW_W);
    		std::cout<<"Norma X = "<<normaX<<", Coord X = "<<x<<", Delta X = "<<deltaX<<std::endl;
    		float normaY = deltaY/(TOP_VIEW_H);
    		std::cout<<"Norma Y = "<<normaY<<", Coord Y = "<<y<<", Delta Y = "<<deltaY<<std::endl;
    		Coord puntoNuevo;
    		puntoNuevo.set(normaX,normaY,0.0f);
    		editorRev->agregarCoordenada(puntoNuevo);
    	}
    	if (boton==MOUSE_RIGHT_BUTTON){
    		editorRev->cerrarCurva();
    	}
    }
    else if(clickPanelInferior(x,y,estado)){
    	if (boton==MOUSE_LEFT_BUTTON){
    		//Obtencion de las coordenadas normalizadas
    	    float deltaX = x-BOTTOM_VIEW_POSX;
    	    float deltaY = (W_HEIGHT-TOP_VIEW_H*0.5)-y;
    	    float normaX = deltaX/(BOTTOM_VIEW_W);
    	    std::cout<<"Norma X = "<<normaX<<", Coord X = "<<x<<", Delta X = "<<deltaX<<std::endl;
    	    float normaY = deltaY/(BOTTOM_VIEW_H);
    	    std::cout<<"Norma Y = "<<normaY<<", Coord Y = "<<y<<", Delta Y = "<<deltaY<<std::endl;
    	    Coord puntoNuevo;
    	    puntoNuevo.set(normaX,normaY,0.0f);
    	    editorPerf->agregarCoordenada(puntoNuevo);
    	}
    	    if (boton==MOUSE_RIGHT_BUTTON){
				editorPerf->definirPerfil();
    	    }
    }
    if (boton==MOUSE_SCROLL_UP){
    	if(cam->distancia() != 1)
    		cam->distancia(cam->distancia() - 1);
    }
    if (boton==MOUSE_SCROLL_DOWN){
    	cam->distancia(cam->distancia() + 1);
    }
}

void keyboard (unsigned char key, int x, int y)
{
   switch (key) {
	case 'q':
                 delete cam;
                 delete roca;
                 delete pasto;
		exit(0);
		break;

	  case 'g':
		  view_grid = !view_grid;
		  glutPostRedisplay();
		  break;

	  case 'A':
		  view_axis = !view_axis;
		  glutPostRedisplay();
		  break;

	  case 'e':
		  edit_panel = !edit_panel;
		  glutPostRedisplay();
		  break;

	  case '2':
		  eye[0] = 0.0;
		  eye[1] = 0.0;
		  eye[2] = 15.0;

		  at[0] = 0.0;
		  at[1] = 0.0;
		  at[2] = 0.0;

		  up[0] = 0.0;
		  up[1] = 1.0;
		  up[2] = 0.0;
		  glutPostRedisplay();
		  break;

	  case '3':
		  eye[0] = 15.0;
		  eye[1] = 15.0;
		  eye[2] = 5.0;

		  at[0] = 0.0;
		  at[1] = 0.0;
		  at[2] = 0.0;

		  up[0] = 0.0;
		  up[1] = 0.0;
		  up[2] = 1.0;
		  glutPostRedisplay();
		  break;
	case 'w':
		glutPostRedisplay();
		break;
	case 'a':
		glutPostRedisplay();
		break;
	case 's':
		glutPostRedisplay();
		break;
	case 'd':
		glutPostRedisplay();
		break;
	case 'y':
		glutPostRedisplay();
		break;
	case 'h':
		glutPostRedisplay();
		break;
	case 'u':
		glutPostRedisplay();
		break;

	case 'j':
		glutPostRedisplay();
		break;

	case 'k':
		glutPostRedisplay();
		break;

	case 'i':
		glutPostRedisplay();
		break;

	case 'o':
		glutPostRedisplay();
		break;

	case 'l':
		glutPostRedisplay();
		break;

	case 'n':
		skybox->switchTime();
		break;
	
	case '-':
		cam->distancia(cam->distancia() + 1);
		break;
	case '+':
		if( cam->distancia() == 1) break;
		cam->distancia(cam->distancia() - 1);
		break;

	case 'f':
		break;

     default:
         break;
   }

}

int main(int argc, char** argv)
{

   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
   glutInitWindowSize (1024, 768); 
   glutInitWindowPosition (0, 0);
   
   glutCreateWindow (argv[0]);
 // glutFullScreen();
   init ();
   glutDisplayFunc(display); 
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutMouseFunc(mouseClick);
   glutPassiveMotionFunc(mouse);
   glutIdleFunc(OnIdle);
   glutMainLoop();
   return 0;
}
